/*
 * DevilSemantic.cpp
 *
 *  Created on: Oct 18, 2012
 *      Author: wtao
 */

#include "DevilSemantic.h"

#ifdef ANDROID
	#define LOG_TAG "DXX"
	#include "devilog.h"
#else
	#include <iostream>
#endif

#include <typeinfo>

DevilParser::DevilParser()
: mLineNo(0)
{
	
}
void DevilParser::addLine()
{
	mLineNo++;
}

void DevilParser::parse(istream &is, onTokenListener &listener)
{
	int c = -1;
	bool expectLF = false;

	do {
		if (c == -1 && !is.eof())
			c = is.get();
		onTokenListener::eContinueParse keepParse = listener.onChar(c);
		if (keepParse == onTokenListener::eStop)
			break;
		if (keepParse == onTokenListener::eLetMeSeeOneMore) {
			if (listener.onChar(is.peek()) == onTokenListener::eStop) {
				is.get();
				break;
			}
		}
		if (expectLF) {
			if (c != LF)
				addLine();
			expectLF = false;
		}

		switch(c) {
			case -1:
				break;
			case '%':
				c = parseComment(is, listener);
				break;
			case LF:
				addLine();
				c = -1;
				break;
			case CR:
				expectLF = true;
				c = -1;
				break;
			case TAB:
			case SP:
			case FF:
			case NUL:
				c = -1;
				break;
			case '+':
			case '-':
			case '.':
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				c = parseNumber(c, is, listener);
				break;
			case '(':
				c = parseLiteral(is, listener);
				break;
			case '<':
				c = is.get();
				if (c == '~') {
					//c = parseLiteralAscii85(is, listener);
				} else if ( c == '<'){
					c = parseDictionary(is, listener);
				} else {
					c = parseLiteralHex(c, is, listener);
				}
				break;
			case '[':
				c = beginArray(is, listener);
				break;
			case ']':
				c = endArray(is, listener);
				break;
			case '{':
				c = parseProcedure(is, listener);
				break;
			default:
				c = parseName(c, is, listener);
				break;
		}
	} while (!is.eof());
}

// The comment consists of all characters between the % and the next newline
int DevilParser::parseComment(istream &is, onTokenListener &listener)
{
	int c = -1;
	bool stop = false;
	stringstream oss;
	bool expectLF = false;

	while (!stop && !is.eof()) {
		c = is.get();

		if (expectLF) {
			if (c != LF) {
				addLine();
				break;
			}
			expectLF = false;
		}

		switch (c) {
			case LF:
				addLine();
				stop = true;
				break;
			case CR:
				expectLF = true;
				oss.put((char) c);
				break;
			default:
				oss.put((char) c);
				break;
		}
	}

	DoComment *po = new DoComment();
	po->setLine(mLineNo);
	po->parse(oss.str());

	CRefPtr< _DoObject > token(po);
	listener.onToken(token);

	return -1;
}

int DevilParser::parseName(int currChar, istream &is, onTokenListener &listener)
{
	int c = currChar;
	bool stop = false, firstChar = true;
	stringstream oss;

	do {
		switch(c) {
			case CR:
			case LF:
			case TAB:
			case SP:
			case FF:
			case NUL:
			case BACKSPACE:
				stop = true;
				break;
			case '/':
				if (!firstChar)
					stop = true;
				else
					oss.put((char)c);
				break;
			case '%':
			case '(':
			case ')':
			case '<':
			case '>':
			case '[':
			case ']':
			case '{':
			case '}':
				stop = true;
				break;
			default:
				oss.put((char)c);
				break;
		}

		if (!stop) {
			if (is.eof()) break;
			c = is.get();
		}
		
		firstChar = false;
	}while (!stop);

	DoName *po = new DoName();
	po->setLine(mLineNo + 1);
	po->parse(oss.str());
	
	CRefPtr< _DoObject > token(po);
	listener.onToken(token);

	return c;
}

int DevilParser::beginArray(istream &is, onTokenListener &listener)
{
	if (typeid(listener) == typeid(DevilSemantic)) {
		// interpret directly encounters an array
		DevilSemantic& state = dynamic_cast< DevilSemantic& >(listener);
		state.getOpStack().mark();
	} else {
		// interpret encounters an array within a procedure
		DoArray *p0 = new DoArray();
		p0->setLine(mLineNo + 1);
		CRefPtr< _DoObject > token = CRefPtr< _DoObject >(p0);

		parse(is, *p0);
		listener.onToken(token);
	}
	return -1;
}

int DevilParser::endArray(istream &is, onTokenListener &listener)
{
	DoArray *p0 = new DoArray();
	p0->setLine(mLineNo + 1);
	CRefPtr< _DoObject > token = CRefPtr< _DoObject >(p0);
	
	DevilSemantic& state = dynamic_cast< DevilSemantic& >(listener);
	CRefPtr< _DoObject > obj;
	do {
		obj = state.getOpStack().pop();
		if (obj->type() != _DoObject::eMark) {
			p0->insert(0, obj);
		}
	} while (obj->type() != _DoObject::eMark);
	
	listener.onToken(token);
	return -1;
}

int DevilParser::parseDictionary(istream &is, onTokenListener &listener) 
{
	DoDictionary *p0 = new DoDictionary("__noname__");
	p0->setLine(mLineNo + 1);
	CRefPtr< _DoObject > token = CRefPtr< _DoObject >(p0);

	parse(is, *(dynamic_cast<onTokenListener *>(p0)));
	listener.onToken(token);

	return -1;	
}

int DevilParser::parseProcedure(istream &is, onTokenListener &listener)
{
	DoProcedure *p0 = new DoProcedure();
	p0->setLine(mLineNo + 1);
	CRefPtr< _DoObject > token = CRefPtr< _DoObject >(p0);

	parse(is, *(dynamic_cast<onTokenListener *>(p0)));
	listener.onToken(token);

	return -1;
}

int DevilParser::parseNumber(int currChar, istream &is, onTokenListener &listener)
{
	int c = currChar;
	bool isReal = false;
	bool isRadix = false;
	bool stop = false;
	stringstream sbuf;

	try {
		do {
			switch(c) {
				case LF:
				case CR:
				case TAB:
				case SP:
					stop = true;
					break;
				case '.':
					if (isReal)
						CXXTHROW;
					isReal = true;
					sbuf.put((char)c);
					break;
				case '#':
					if (isRadix)
						CXXTHROW;
					isRadix = true;
					sbuf.put((char)c);
					break;
				default:
					if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '-')
						sbuf.put((char)c);
					else
						stop = true;
					break;
			}

			if (!stop) {
				if (is.eof()) break;
				c = is.get();
			}
		}while (!stop);

		DoNumber *token = new DoNumber(0);
		token->setLine(mLineNo + 1);
		token->parse(isReal, isRadix, sbuf.str());
		listener.onToken(token);
	} catch (exception& e) {
		sbuf.seekg(0, ios::beg);
		parseName(currChar, sbuf, listener);
	}

	return c;
}

int DevilParser::parseLiteral(istream &is, onTokenListener &listener)
{
	int c = -1;
	int leftCount = 1;
	bool stop = false;
	bool escapeMode = false;
	bool expectLF = false;
	int threeDigit = 0;
	ostringstream sbuf;
	ostringstream sdigits;
	
	while (!stop && !is.eof()) {
		c = is.get();
		
		if (expectLF) {
			if (c != LF)
				addLine();
			expectLF = false;
		}
		
		if (escapeMode && threeDigit != 0) {
			switch(c) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
					break;
				default:
					threeDigit = 0;
					escapeMode = false;
					sbuf.put((char)strtol(sdigits.str().c_str(), 0, 8));
					break;
			}
		}
		switch(c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				if (escapeMode) {
					if (threeDigit == 0) {
						threeDigit = 3;
					}
					sdigits.put(c);
					threeDigit--;
					if (threeDigit == 0) {
						escapeMode = false;
						sbuf.put((char)strtol(sdigits.str().c_str(), 0, 8));
					}
				} else {
					sbuf.put((char)c);
				}
				break;
			case '(':
				if (escapeMode) {
					sbuf.put('(');
					escapeMode = false;
				} else {
					leftCount++;
					sbuf.put((char)c);
				}
				break;
			case ')':
				if (escapeMode) {
					sbuf.put(')');
					escapeMode = false;
				} else {
					leftCount--;
					if (leftCount == 0) {
						stop = true;
					} else {
						sbuf.put((char)c);
					}
				}
				break;
			case '\\':
				if (escapeMode) {
					sbuf.put('\\');
					escapeMode = false;
				} else {
					escapeMode = true;
				}
				break;
			case 'n':
			case 'r':
			case 't':
			case 'b':
			case 'f':
				if (escapeMode) {
					switch(c) {
					case 'n':
						sbuf.put(LF);
						break;
					case 'r':
						sbuf.put(CR);
						break;
					case 't':
						sbuf.put(TAB);
						break;
					case 'b':
						sbuf.put(BACKSPACE);
						break;
					case 'f':
						sbuf.put(FF);
						break;
					}
					escapeMode = false;
				} else {
					sbuf.put((char)c);
				}
				break;
			case LF:
				addLine();
				if (escapeMode) {
					escapeMode = false;
				} else {
					sbuf.put((char)c);
				}
				break;
			case CR:
				if (escapeMode) {
				} else {
					expectLF = true;
					sbuf.put((char)c);
				}
				break;
			default:
				if (escapeMode)
					escapeMode = false;
				sbuf.put((char)c);
				break;
		}
	}
	if (leftCount != 0) CXXTHROW;
	
	DoString *token = new DoString();
	token->setLine(mLineNo + 1);
	token->setText(sbuf.str());
	listener.onToken(token);
		
	return -1;
}

int DevilParser::parseLiteralHex(int currChar, istream &is, onTokenListener &listener)
{
	int c = currChar;
	bool stop = false, expectLF = false;
	ostringstream sbuf;
	
	do {
		if (expectLF) {
			if (c != LF)
				addLine();
			expectLF = false;
		}
		
		switch(c) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case 'A':
			case 'a':
			case 'B':
			case 'b':
			case 'C':
			case 'c':
			case 'D':
			case 'd':
			case 'E':
			case 'e':
			case 'F':
			case 'f':
				sbuf.put((char)c);
				break;
			case '>':
				stop = true;
				break;
			case LF:
				addLine();
				break;
			case CR:
				expectLF = true;
				break;
			case TAB:
			case SP:
			case FF:
			case NUL:
			case BACKSPACE:
				break;
			default:
				CXXTHROW;
		}
		
		if (!stop) {
			if (is.eof()) break;
			c = is.get();
		}
	} while (!stop);
	
	DoString *token = new DoString();
	token->setLine(mLineNo + 1);
	std::string blob = sbuf.str();
	token->parseHex(blob);
	listener.onToken(token);
		
	return -1;
}

int DevilParser::parseLiteralAscii85(istream &is, onTokenListener &listener) 
{
	int c = -1;
	bool stop = false, expectLF = false;
	std::ostringstream sbuf;
	
	while (!stop && !is.eof()) {
		c = is.get();
		
		if (expectLF) {
			if (c != LF)
				addLine();
			expectLF = false;
		}
		
		switch(c) {
			case '~':
				stop = true;
				sbuf.put((char)c);
				c = is.get();
				if (c != '>')
					CXXTHROW;
				break;
			case LF:
				addLine();
				break;
			case CR:
				expectLF = true;
				break;
			case TAB:
			case SP:
			case FF:
			case NUL:
			case BACKSPACE:
				break;
			default:
				sbuf.put((char)c);
				break;
		}
	}
		
	DoString *token = new DoString();
	token->setLine(mLineNo + 1);
	std::string blob = sbuf.str();
	token->parseAscii85(blob);
	listener.onToken(token);
	
	return -1;
}
 
DoDictionaryStack::DoDictionaryStack()
{
	_DoDictionaryMultimap *p0 = new _DoDictionaryMultimap("_systemdict");
	DoOpArithmetic::push(*p0);
	DoOpArray::push(*p0);
	DoOpBoolean::push(*p0);
	DoOpControl::push(*p0);
	DoOpDictionary::push(*p0);
	DoOpPackedArray::push(*p0);
	DoOpStack::push(*p0);
	DoOpString::push(*p0);
	DoOpType::push(*p0);
	DoOpMiscellaneous::push(*p0);
	DoOpGrphStateDI::push(*p0);
	DoOpGrphStateDD::push(*p0);
	DoOpCoordinateMatrix::push(*p0);
	DoOpPath::push(*p0);
	DoOpPaint::push(*p0);
	DoOpFont::push(*p0);
	DoOpDeviceOutput::push(*p0);
	DoOpFileRes::push(*p0);
	p0->setAccessType(_DoCompositeObject::eReadOnly);
	_systemdict = CRefPtr< _DoObject >(p0);

	_globaldict = CRefPtr< _DoObject >(new DoDictionary("_globaldict"));
	push(_globaldict);

	_userdict = CRefPtr< _DoObject >(new DoDictionary("_userdict"));
	push(_userdict);
}

CRefPtr< _DoObject > DoDictionaryStack::findDict(CRefPtr< _DoObject > key)
{
	CRefPtr< _DoObject > value;
	
	for(size_t i = 0; i < size(); i++) {
		CRefPtr< _DoObject > p0 = top(i);
		DoDictionary *dict = toDict(p0);
		if (dict->containsKey(key))
			return p0;
	}

	if (dynamic_cast<_DoDictionaryMultimap *>(_systemdict.get())->containsKey(key)) {
		return _systemdict;
	}
		
	return CRefPtr< _DoObject >();
}

vector< CRefPtr< _DoObject > > DoDictionaryStack::find(CRefPtr< _DoObject > key)
{
	vector< CRefPtr< _DoObject> > out;
	
	for(size_t i = 0; i < size(); i++) {
		CRefPtr< _DoObject > p0 = top(i);
		DoDictionary *dict = toDict(p0);
		if (dict->containsKey(key)) {			
			CRefPtr< _DoObject > value = dict->get(key);
			out.push_back(value);
		}
	}

	if (dynamic_cast<_DoDictionaryMultimap *>(_systemdict.get())->containsKey(key)) {
		_DoDictionaryMultimap::CDictRange range = dynamic_cast<_DoDictionaryMultimap *>(_systemdict.get())->get(key);
		for(_DoDictionaryMultimap::CObjMultimap::iterator it = range.first; it != range.second; ++it) {
			out.push_back((*it).second);
		}
	}

	return out;
}

CRefPtr< _DoObject > DoDictionaryStack::lookup(CRefPtr< _DoObject > key, DevilSemantic &state)
{
	vector< CRefPtr< _DoObject > > list = find(key);
	CRefPtr< _DoObject > value;
	
	if (list.size() == 1) {
		value = list.at(0);
		value->setLine(key->getLine());
		return value;		//direct match
	} else if (list.size() > 1) { 
		for(size_t i = 0; i < list.size(); i++) {
			value = list.at(i);
			
			if (value->type() != _DoObject::eOperator)
				return value; //return the the first encountered
				
			if (dynamic_cast< _DoOperator * >(value.get())->match(state)) {
				value->setLine(key->getLine());
				return value;				//match correct operator
			}
		}

		return CRefPtr< _DoObject >();; //not enough context to match a proper operator, for example, when run under bind operator
	}

	return CRefPtr< _DoObject >();
}

void DoDictionaryStack::put(CRefPtr< _DoObject > key, CRefPtr< _DoObject > value)
{
	CRefPtr< _DoObject > p0 = top(0);
	DoDictionary *dict = toDict(p0);
	
	if (dict->accessType() == _DoCompositeObject::eReadOnly)
		CXXTHROW;
		
	//If key is already present in the current
	//dictionary, def simply replaces its value; otherwise, def creates a new entry for key
	//and stores value with it.
	dict->put(key, value);


	// 3.5.3
	// There is a special rule that determines this behavior: An executable
	// array or packed array encountered directly by the interpreter is treated as data
	// (pushed on the operand stack), but an executable array or packed array encountered
	// indirectly—as a result of executing some other object, such as a name or an
	// operator—is invoked as a procedure.
	if (value->type() == _DoObject::eProcedure) {
		((DoProcedure *)value.get())->enableExecutable();
		value->setText(key->getText());
	}
}

CRefPtr< _DoObject > DoDictionaryStack::globaldict()
{
	return _globaldict;
}

CRefPtr< _DoObject > DoDictionaryStack::userdict()
{
	return _userdict;
}
	
CRefPtr< _DoObject > DoDictionaryStack::systemdict()
{
	return _systemdict;
}
	
DevilSemantic::DevilSemantic()
: onTokenListener()
, mStdInput(0)
, mGraphic()
, mStackOp()
, mStackDict()
, mFontDict("FontDict")
, mParser()
{
}

DevilSemantic::~DevilSemantic() {
	// TODO Auto-generated destructor stub
}

void DevilSemantic::parse()
{
	mParser.parse(*mStdInput, *this);
}

void DevilSemantic::onToken(CRefPtr< _DoObject > token)
{
	_debug_dump(token);

	switch(token->type()) {
		case _DoObject::eComment:
			break;
		case _DoObject::eArray:
		case _DoObject::eBoolean:
		case _DoObject::eDictionary:
		case _DoObject::eNumber:
		case _DoObject::eProcedure:
		case _DoObject::eString:
		case _DoObject::eName:
			if (token->executable()) {
				token->execute(*this);
			} else {
				getOpStack().push(token);
			}
			break;
		default:
			CXXTHROW
	}
}

onTokenListener::eContinueParse DevilSemantic::onChar(int currChar)
{
	return onTokenListener::eContinue;
}

void DevilSemantic::_debug_dump(CRefPtr< _DoObject > token)
{
#ifdef LANG_DEBUG
	ostringstream oss;
	std::string type;
	switch(token->type()) {
		case _DoObject::eComment:
			type = "eComment";
			break;
		case _DoObject::eArray:
			type = "eArray";
			break;
		case _DoObject::eBoolean:
			type = "eBoolean";
			break;
		case _DoObject::eDictionary:
			type = "eDictionary";
			break;
		case _DoObject::eNumber:
			type = "eNumber";
			break;
		case _DoObject::eProcedure:
			type = "eProcedure";
			break;
		case _DoObject::eString:
			type = "eString";
			break;
		case _DoObject::eName:
			type = "eName";
			break;
		default:
			CXXTHROW
	}
	oss << token->getLine() << " - " << type.c_str() << ": "  << token->_debug_dump().c_str() << std::endl;
	outputDebugString(oss.str());
#endif
}

void DevilSemantic::outputDebugString(const std::string& str) 
{
#ifdef LANG_DEBUG

#ifdef ANDROID
	LOGI("%s", str.c_str());
#else
	std::cout << str;
	std::cout.flush();
#endif

#endif
}

const char * ptoken(CRefPtr< _DoObject > token)
{
	return token->_debug_dump().c_str();
}

const char * ptoken(_DoObject &token)
{
	return token._debug_dump().c_str();
}

const char * DevilSemantic::pstack()
{
	return mStackOp._debug_dump().c_str();
}

const char * DevilSemantic::pdictstack()
{
	return mStackDict._debug_dump().c_str();
}

